;(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(factory)
  } else if (typeof module === 'object' && module.exports) {
    module.exports = factory()
  } else {
    root.Popper = factory()
  }
})(this, function() {
  'use strict';

  var root = window

  var DEFAULTS = {
    placement: 'bottom',

    gpuAcceleration: true,

    offset: 0,

    boundariesElement: 'viewport',

    boundariesPadding: 5,

    preventOverflowOrder: ['left', 'right', 'top', 'bottom'],

    flipBehavior: 'flip',

    arrowElement: '[x-arrow]',

    modifiers: [ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle' ],

    modifiersIgnored: [],

    forceAbsolute: false,
  }

  /**
   * Create a new Popper.js instance
   * @constructor Popper
   * @param {HTMLElement} reference - The reference element used to position the popper
   * @param {HTMLElement|Object} popper
   *      The HTML element used as popper, or a configuration used to generate the popper.
   * @param {String} [popper.tagName='div'] The tag name of the generated popper.
   * @param {Array} [popper.classNames=['popper']] Array of classes to apply to the generated popper.
   * @param {Array} [popper.attributes] Array of attributes to apply, specify `attr:value` to assign a value to it.
   * @param {HTMLElement|String} [popper.parent=window.document.body] The parent element, given as HTMLElement or as query string.
   * @param {String} [popper.content=''] The content of the popper, it can be text, html, or node; if it is not text, set `contentType` to `html` or `node`.
   * @param {String} [popper.contentType='text'] If `html`, the `content` will be parsed as HTML. If `node`, it will be appended as-is.
   * @param {String} [popper.arrowTagName='div'] Same as `popper.tagName` but for the arrow element.
   * @param {Array} [popper.arrowClassNames='popper__arrow'] Same as `popper.classNames` but for the arrow element.
   * @param {String} [popper.arrowAttributes=['x-arrow']] Same as `popper.attributes` but for the arrow element.
   * @param {Object} options
   * @param {String} [options.placement=bottom]
   *      Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -right),
   *      left(-start, -end)`
   *
   * @param {HTMLElement|String} [options.arrowElement='[x-arrow]']
   *      The DOM Node used as arrow for the popper, or a CSS selector used to get the DOM node. It must be child of
   *      its parent Popper. Popper.js will apply to the given element the style required to align the arrow with its
   *      reference element.
   *      By default, it will look for a child node of the popper with the `x-arrow` attribute.
   *
   * @param {Boolean} [options.gpuAcceleration=true]
   *      When this property is set to true, the popper position will be applied using CSS3 translate3d, allowing the
   *      browser to use the GPU to accelerate the rendering.
   *      If set to false, the popper will be placed using `top` and `left` properties, not using the GPU.
   *
   * @param {Number} [options.offset=0]
   *      Amount of pixels the popper will be shifted (can be negative).
   *
   * @param {String|Element} [options.boundariesElement='viewport']
   *      The element which will define the boundaries of the popper position, the popper will never be placed outside
   *      of the defined boundaries (except if `keepTogether` is enabled)
   *
   * @param {Number} [options.boundariesPadding=5]
   *      Additional padding for the boundaries
   *
   * @param {Array} [options.preventOverflowOrder=['left', 'right', 'top', 'bottom']]
   *      Order used when Popper.js tries to avoid overflows from the boundaries, they will be checked in order,
   *      this means that the last ones will never overflow
   *
   * @param {String|Array} [options.flipBehavior='flip']
   *      The behavior used by the `flip` modifier to change the placement of the popper when the latter is trying to
   *      overlap its reference element. Defining `flip` as value, the placement will be flipped on
   *      its axis (`right - left`, `top - bottom`).
   *      You can even pass an array of placements (eg: `['right', 'left', 'top']` ) to manually specify
   *      how alter the placement when a flip is needed. (eg. in the above example, it would first flip from right to left,
   *      then, if even in its new placement, the popper is overlapping its reference element, it will be moved to top)
   *
   * @param {Array} [options.modifiers=[ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle']]
   *      List of functions used to modify the data before they are applied to the popper, add your custom functions
   *      to this array to edit the offsets and placement.
   *      The function should reflect the @params and @returns of preventOverflow
   *
   * @param {Array} [options.modifiersIgnored=[]]
   *      Put here any built-in modifier name you want to exclude from the modifiers list
   *      The function should reflect the @params and @returns of preventOverflow
   *
   * @param {Boolean} [options.removeOnDestroy=false]
   *      Set to true if you want to automatically remove the popper when you call the `destroy` method.
   */
  function Popper(reference, popper, options) {
    this._reference = reference.jquery ? reference[0] : reference
    this.state = {}

    var isNotDefined = typeof popper === 'undefined' || popper === null
    var isConfig = popper && Object.prototype.toString.call(popper) === '[object Object]'
    if (isNotDefined || isConfig) {
      this._popper = this.parse(isConfig ? popper : {})
    } else {
      this._popper = popper.jquery ? popper[0] : popper
    }

    this._options = Object.assign({}, DEFAULTS, options)

    this._options.modifiers = this._options.modifiers.map(function(modifier) {
      if (this._options.modifiersIgnored.indexOf(modifier) !== -1) return
      if (modifier === 'applyStyle') {
        this._popper.setAttribute('x-placement', this._options.placement)
      }

      return this.modifiers[modifier] || modifier
    }).bind(this)
    this.state.position = this._getPosition(this._popper, this._reference)
    setStyle(this._popper, { position: this.state.position, top: 0 })

    this.update()

    this._setupEventListeners()

    return this
  }

  Popper.prototype.destroy = function() {
    this._popper.removeAttribute('x-placement')
    this._popper.style.left = ''
    this._popper.style.position = ''
    this._popper.style.top = ''
    this._popper.style[getSupportedPropertyName('transform')] = ''
    this._removeEventListeners()

    if (this._options.removeOnDestroy) {
      this._popper.remove()
    }
    return this
  }

  Popper.prototype.update = function() {
    var data = { instance: this, styles: {} }

    data.placement = this._options.placement
    data._originalPlacement = this._options.placement
    data.offsets = this._getOffsets(this._popper, this._reference, data.placement)
    data.boundaries = this._getBoundaries(data, this._options.boundariesPadding, this._options.boundariesElement)

    data = this.runModifiers(data, this._options.modifiers)

    if (typeof this.state.updateCallback === 'function') {
      this.state.updateCallback(data)
    }
  }

  Popper.prototype.onCreate = function(callback) {
    callback(this)
    return this
  }

  Popper.prototype.onUpdate = function(callback) {
    this.state.updateCallback = callback
    return this
  }

  Popper.prototype.parse = function(config) {
    var defaultConfig = {
      tagName: 'div',
      classNames: [ 'popper' ],
      attributes: [],
      parent: root.document.body,
      content: '',
      contentType: 'text',
      arrowTagName: 'div',
      arrowClassNames: [ 'popper__arrow' ],
      arrowAttributes: [ 'x-arrow']
    }
    config = Object.assign({}, defaultConfig, config)

    var d = root.document

    var popper = d.createElement(config.tagName)
    addClassNames(popper, config.classNames)
    addAttributes(popper, config.attributes)
    if (config.contentType === 'node') {
      popper.appendChild(config.content.jquery ? config.content[0] : config.content)
    } else if (config.contentType === 'html') {
      popper.innerHTML = config.content
    } else {
      popper.textContent = config.content
    }

    if (config.arrowTagName) {
      var arrow = d.createElement(config.arrowTagName)
      addClassNames(arrow, config.arrowClassNames)
      addAttributes(arrow, config.arrowAttributes)
      popper.appendChild(arrow)
    }

    var parent = config.parent.jquery ? config.parent[0] : config.parent
    if (typeof parent === 'string') {
        parent = d.querySelectorAll(config.parent);
        if (parent.length > 1) {
            console.warn('WARNING: the given `parent` query(' + config.parent + ') matched more than one element, the first one will be used');
        }
        if (parent.length === 0) {
            throw 'ERROR: the given `parent` doesn\'t exists!';
        }
        parent = parent[0];
    }
    if (parent.length > 1 && parent instanceof Element === false) {
        console.warn('WARNING: you have passed as parent a list of elements, the first one will be used');
        parent = parent[0];
    }
    parent.appendChild(popper);

    return popper;

    function addClassNames(element, classNames) {
      classNames.forEach(function(className) {
        element.classList.add(className)
      })
    }

    function addAttributes(element, attribute) {
      attributes.forEach(function(attribute) {
        element.setAttribute(attribute.split(':')[0], attribute.split(':')[1] || '')
      })
    }
  }

  Popper.prototype._getPosition = function(popper, reference) {
    var container = getOffsetParent(reference)

    if (this._options.forceAbsolute) {
      return 'absolute'
    }

    var isParentFixed = isFixed(reference, container)
    return isParentFixed ? 'fixed' : 'absolute'
  }

  
})
